package be.kuleuven.mume.hulpklassen;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import be.kuleuven.mume.framework.AgendaItem;
import be.kuleuven.mume.framework.Antwoord;
import be.kuleuven.mume.framework.Persoon;
import be.kuleuven.mume.framework.Prof;
import be.kuleuven.mume.framework.Update;
import be.kuleuven.mume.framework.Vak;
import be.kuleuven.mume.framework.Vraag;

import android.util.Log;

public abstract class JSONParser<DataType> {
	private static final String TAG = "MumeParser";
	public static final String RESULT = "result";

	/**
	 * Parse the jString to framework objects by using the set<field>(String field) methods.
	 * Override tryParseObject(JSONObject obj) to parse additional info.
	 * 
	 * @param jString json with a JSONArray 'result'
	 * @return List with the requested Objects.
	 */
	public List<DataType> parse(String jString){
		try {
			if(jString == null || jString.equals(""))
				return Collections.emptyList();

			JSONObject jObject = new JSONObject(jString);

			if(jObject.has("error"))
				Log.e(TAG,"MobiLENviServer", new Exception(jObject.getString("error")));

			JSONArray result  = jObject.getJSONArray(RESULT);

			ArrayList<DataType> dataObjects = new ArrayList<DataType>();
			for(int i = 0; i < result.length(); i++) {
				JSONObject obj = result.getJSONObject(i);
				DataType o = tryParseObject(obj);
				if(o != null)
					dataObjects.add(o);
			}

			return dataObjects;
		}catch (JSONException e) {
			Log.e(TAG,"JSONException",e);
			Log.d(TAG,"JSONString: " + jString);
		} catch (Exception e) {
			Log.e(TAG,"Exception",e);
		}
		return Collections.emptyList();
	}

	@SuppressWarnings ("unchecked")
	public Class<DataType> getTypeParameterClass()
	{
		Type type = this.getClass().getGenericSuperclass();
		ParameterizedType paramType = (ParameterizedType) type;
		return (Class<DataType>) paramType.getActualTypeArguments()[0];
	}

	/**
	 * Parses the JSONObject to the requested Object using java reflections.
	 * Only String fields are set.
	 * TODO: Add extra field types.
	 * 
	 * @param obj JSONObject that needs to be parsed
	 * @return parsed framework object
	 * @throws JSONException
	 */
	public DataType tryParseObject(JSONObject obj) throws JSONException {
		try {
			Class<DataType> c = getTypeParameterClass();
			DataType t = c.newInstance();

			Method[] allMethods = c.getDeclaredMethods();
			for (Method m : allMethods) {
				String mname = m.getName();
				if (!mname.startsWith("set") || (m.getGenericReturnType() != void.class)) {
					continue;
				}
				Type[] pType = m.getGenericParameterTypes();
				if((pType.length != 1)
						|| !(pType[0].equals(String.class) || pType[0].equals(Integer.class)))
					continue;

				String oname = mname.substring(3);
				oname = oname.substring(0, 1).toLowerCase() + oname.substring(1);

				Object defaultValue;
				if(pType[0].equals(String.class))
					defaultValue = "";
				else if(pType[0].equals(Integer.class))
					defaultValue = -1;
				else
					defaultValue = null;

				if(obj.has(oname))
					m.invoke(t,obj.get(oname));
				else
					m.invoke(t,defaultValue);
			}

			return t;
		} catch (InstantiationException x) {
			x.printStackTrace();
		} catch (IllegalAccessException x) {
			x.printStackTrace();
		} catch (InvocationTargetException x) {
			x.printStackTrace();
		}

		return null;
	}

	public static class UpdatesParser extends JSONParser<Update>{
		@Override
		public Update tryParseObject(JSONObject obj) throws JSONException {
			// TODO Auto-generated method stub
			Update u = super.tryParseObject(obj);

			if(obj.has("datestamp")){
				Date d = new Date(obj.getString("datestamp"));
				u.setDatum(d);
			}
			return u;
		}
	}

	public static class AntwoordenParser extends JSONParser<Antwoord>{}

	public static class VragenParser extends JSONParser<Vraag>{
		private static final String ANTWOORDEN = "antwoorden";

		@Override
		public Vraag tryParseObject(JSONObject obj) throws JSONException {
			Vraag vraag = super.tryParseObject(obj);
			if(obj.has(ANTWOORDEN))
			{
				JSONArray antwoorden = obj.getJSONArray(ANTWOORDEN);
				for (int i = 0 ; i < antwoorden.length() ; i++) {
					vraag.addAntwoord(antwoorden.getString(i));
				}
			}
			return vraag;
		}
	}
	public static class VakkenParser extends JSONParser<Vak>{
		public static final String PROF = "prof";
		@Override
		public Vak tryParseObject(JSONObject obj) throws JSONException {
			Vak vak = super.tryParseObject(obj);
			Prof prof = null;
			if(obj.has(PROF)) {
				ProfParser p = new ProfParser();
				prof = p.tryParseObject(obj.getJSONObject(PROF));
			}
			vak.setProf(prof);

			return vak;
		}
	}

	public static class VakParser extends VakkenParser{
		@Override
		public List<Vak> parse(String jString){
			try {
				JSONObject jObject = new JSONObject(jString);

				if(jObject.has("error"))
					Log.e(TAG,"MobiLENviServer", new Exception(jObject.getString("error")));

				JSONObject result  = jObject.getJSONObject(RESULT);

				ArrayList<Vak> dataObjects = new ArrayList<Vak>();
				Vak o = tryParseObject(result);

				if(o != null)
					dataObjects.add(o);

				return dataObjects;
			}catch (JSONException e) {
				Log.e(TAG,"JSONException",e);
				Log.d(TAG,"JSONString: " + jString);
			} catch (Exception e) {
				Log.e(TAG,"Exception",e);
			}
			return Collections.emptyList();
		}

		@Override
		public Class<Vak> getTypeParameterClass()
		{
			return (Class<Vak>) Vak.class;
		}
	}

	public static class ProfParser extends JSONParser<Prof>{}

	public static class PersoonParser extends JSONParser<Persoon>{}

	public static class AgendaParser extends JSONParser<AgendaItem>{
		private boolean asUpdate = false;

		public AgendaParser(boolean asUpdate){
			this.asUpdate = asUpdate;
		}
		@Override
		public AgendaItem tryParseObject(JSONObject obj) throws JSONException {
			AgendaItem agenda = super.tryParseObject(obj);

			agenda.setDatum(trygetDateField(obj, agenda, "datum"));
			agenda.setTimeStamp(trygetDateField(obj, agenda, "timeStamp"));

			agenda.setAsUpdate(asUpdate);

			return agenda;
		}
		private Date trygetDateField(JSONObject obj, AgendaItem agenda, String field)
		throws JSONException {
			if(obj.has(field)) {
				Date d = new Date(obj.getString(field));
				return d;
			}
			return new Date();
		}
	}
}
